/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Bsp
{
	using Graphics;

	internal sealed class BspReader : ContentTypeReader<BspData>
	{
		private Entity CreateEntity( int typeId )
		{
			switch( typeId )
			{
			case 0: return new Entity();
			case 1: return new LightEntity();
			case 2: return new WorldSpawnEntity();

			default:
				throw new ContentLoadException();
			}
		}

		protected override BspData Read( ContentReader input, BspData existingInstance )
		{
			IGraphicsDeviceService devSvc =
				input.ContentManager.ServiceProvider.GetService(
				typeof( IGraphicsDeviceService ) ) as IGraphicsDeviceService;

			if( devSvc == null )
				throw new InvalidOperationException( "no device?" );

			IMaterialService matSvc =
				input.ContentManager.ServiceProvider.GetService(
				typeof( IMaterialService ) ) as IMaterialService;

			if( matSvc == null )
				throw new InvalidOperationException( "no material service, dude?" );

			int numEnts = input.ReadInt32();
			int[] targetLinkages = new int[numEnts];
			Entity[] entities = new Entity[numEnts];

			List<LightEntity> lights = new List<LightEntity>();

			for( int i = 0; i < entities.Length; i++ )
			{
				Entity ent = CreateEntity( input.ReadInt32() );
				targetLinkages[i] = input.ReadInt32();
				ent.Read( input );

				if( ent is LightEntity )
					lights.Add( (LightEntity)ent );

				entities[i] = ent;
			}

			for( int i = 0; i < entities.Length; i++ )
			{
				if( targetLinkages[i] != -1 )
					entities[i].Target = entities[targetLinkages[i]];
			}			

			Material[] materials = new Material[input.ReadInt32()];
			for( int i = 0; i < materials.Length; i++ )
			{
				materials[i] = input.ReadObject<Material>();
			}

			SurfaceBuffer[] buffers = new SurfaceBuffer[input.ReadInt32()];
			for( int i = 0; i < buffers.Length; i++ )
			{
				VertexDeclaration decl = input.ReadObject<VertexDeclaration>();
				VertexBuffer vb = input.ReadObject<VertexBuffer>();
				IndexBuffer ib = input.ReadObject<IndexBuffer>();

				buffers[i] = new SurfaceBuffer( decl, vb, ib );
			}

			Material defMat = matSvc.MaterialManager.GetMaterial( "Default" );

			Surface[] surfaces = new Surface[input.ReadInt32()];
			for( int i = 0; i < surfaces.Length; i++ )
			{
				Surface surf = new Surface( buffers[input.ReadInt32()] );

				int matIdx = input.ReadInt32();
				surf.Material = matIdx != -1 ? materials[matIdx] : defMat;

				surf.FirstVertex = input.ReadInt32();
				surf.VertexCount = input.ReadInt32();
				surf.FirstIndex = input.ReadInt32();
				surf.PrimitiveCount = input.ReadInt32();

				surf.surfaceSort = i;

				surfaces[i] = surf;
			}

			Brush[] brushes = new Brush[input.ReadInt32()];
			for( int i = 0; i < brushes.Length; i++ )
			{
				Brush br = new Brush();
				br.ContentFlags = (BrushContents)input.ReadInt32();

				BrushSide[] sides = new BrushSide[input.ReadInt32()];
				for( int j = 0; j < sides.Length; j++ )
				{
					BrushSide side = new BrushSide();

					side.Plane = input.ReadRawObject<Plane>();
					side.SurfaceFlags = input.ReadInt32();

					sides[j] = side;
				}

				br.Sides = sides;
				brushes[i] = br;
			}

			Surface[] noSurfs = new Surface[0];
			Brush[] noBrushes = new Brush[0];

			LeafNode[] leaves = new LeafNode[input.ReadInt32()];
			for( int i = 0; i < leaves.Length; i++ )
			{
				LeafNode leaf = new LeafNode();

				leaf.bounds = input.ReadRawObject<BoundingBox>();
				leaf.pvsCluster = input.ReadInt32();

				int brushCount = input.ReadInt32();
				if( brushCount != 0 )
				{
					leaf.Brushes = new Brush[brushCount];
					for( int j = 0; j < leaf.Brushes.Length; j++ )
						leaf.Brushes[j] = brushes[input.ReadInt32()];
				}
				else
				{
					leaf.Brushes = noBrushes;
				}

				int surfaceCount = input.ReadInt32();
				if( surfaceCount != 0 )
				{
					leaf.surfaces = new Surface[surfaceCount];
					for( int j = 0; j < leaf.surfaces.Length; j++ )
						leaf.surfaces[j] = surfaces[input.ReadInt32()];
				}
				else
				{
					leaf.surfaces = noSurfs;
				}

				leaves[i] = leaf;
			}

			PartitionNode[] partitions = new PartitionNode[input.ReadInt32()];
			for( int i = 0; i < partitions.Length; i++ )
				partitions[i] = new PartitionNode();
			for( int i = 0; i < partitions.Length; i++ )
			{
				PartitionNode part = partitions[i];

				part.bounds = input.ReadRawObject<BoundingBox>();
				part.plane = input.ReadRawObject<Plane>();

				int childNum;

				childNum = input.ReadInt32();
				if( childNum != 0 )
				{
					part.child0 = childNum > 0 ? (Node)partitions[childNum - 1] : (Node)leaves[-childNum - 1];
					part.child0.Parent = part;
				}

				childNum = input.ReadInt32();
				if( childNum != 0 )
				{
					part.child1 = childNum > 0 ? (Node)partitions[childNum - 1] : (Node)leaves[-childNum - 1];
					part.child1.Parent = part;
				}

				int surfaceCount = input.ReadInt32();
				if( surfaceCount != 0 )
				{
					part.surfaces = new Surface[surfaceCount];
					for( int j = 0; j < part.surfaces.Length; j++ )
						part.surfaces[j] = surfaces[input.ReadInt32()];
				}
				else
				{
					part.surfaces = noSurfs;
				}
			}

			PvsData pvs = new PvsData();
			pvs.NumClusters = input.ReadInt32();
			pvs.PvsBytes = new uint[input.ReadInt32()];
			for( int i = 0; i < pvs.PvsBytes.Length; i++ )
				pvs.PvsBytes[i] = input.ReadUInt32();

			BspData ret = new BspData();

			ret.GraphicsDevice = devSvc.GraphicsDevice;
			ret.Entities.All = entities;
			ret.Entities.Lights = lights.ToArray();
			ret.Surfaces = surfaces;
			ret.Brushes = brushes;
			ret.Leaves = leaves;
			ret.Partitions = partitions;
			ret.PvsData = pvs;

			ret.Loaded();

			return ret;
		}
	}
}